package plus.chendd.lock;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.params.SetParams;

import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

@Component
public class RedisDistLock implements Lock {
  
  // 尝试着的加锁时间
  private final static int LOCK_TIME = 5 * 1000;
  // redis 的内容标识
  private final static String RS_DISTLOCK_NS = "tdln:";
  
  // 使用本地线程 保证每个id的独立性
  private final ThreadLocal<String> lockerId = new ThreadLocal<>();
  
  // 解决锁重入的问题
  private Thread ownerThread;
  private final String lockName = "lock";
  
  private final static String RELEASE_LOCK_LUA = "if redis.call('get',KEYS[1])==ARGV[1] then\n" + "        return redis.call('del', KEYS[1])\n" + "    else return 0 end";
  
  
  @Autowired
  private JedisPool jedisPool;
  
  @Override
  public void lock() {
    // 每间隔100ms 就不断的尝试抢锁
    while (!tryLock()) {
      try {
        Thread.sleep(100);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }
  
  @Override
  public void lockInterruptibly() throws InterruptedException {
    throw new UnsupportedOperationException("不支持可中断获取锁");
  }
  
  @Override
  public boolean tryLock() {
    Thread t = Thread.currentThread();
    // 说明本线程就是锁的持有者 无需再次加锁
    if (ownerThread == t) {
      return true;
      
      // 说明本线程不是锁的持有者 但是锁已经被其他线程持有
    } else if (ownerThread != null) {
      return false;
    }
    
    Jedis jedis = null;
    try {
      jedis = jedisPool.getResource();
      String uuid = UUID.randomUUID().toString();
      
      SetParams setParams = new SetParams();
      setParams.px(LOCK_TIME);
      setParams.nx();
      
      // 加锁 避免并发问题
      synchronized (this) {
        if (ownerThread == null && ("OK".equals(jedis.set(RS_DISTLOCK_NS + lockName, uuid, setParams)))) {
          // 表示加锁成功
          lockerId.set(uuid);
          System.out.println(Thread.currentThread().getName() + ": 抢锁成功");
          ownerThread = t;
          return true;
        } else {
          return false;
        }
      }
      
    } catch (Exception e) {
      throw new RuntimeException("分布式锁尝试加锁失败！");
    } finally {
      if (jedis != null) jedis.close();
    }
  }
  
  @Override
  public boolean tryLock(long time, @NonNull TimeUnit unit) throws InterruptedException {
    throw new UnsupportedOperationException("不支持等待尝试获取锁");
  }
  
  @Override
  public void unlock() {
    if (ownerThread != Thread.currentThread()) {
      throw new RuntimeException("试图释放无所有权的锁！");
    }
    
    Jedis jedis = null;
    try {
      jedis = jedisPool.getResource();
      
      Long result = (Long) jedis.eval(RELEASE_LOCK_LUA, Collections.singletonList(RS_DISTLOCK_NS + lockName), Collections.singletonList(lockerId.get()));
      if (result != 0L) {
        System.out.println("Redis上的锁已释放！");
      } else {
        System.out.println("Redis上的锁释放失败！");
      }
    } catch (Exception e) {
      throw new RuntimeException("锁释放失败！");
    } finally {
      if (jedis != null) jedis.close();
      lockerId.remove();
      ownerThread = null;
      System.out.println(Thread.currentThread().getName() + ": 释放锁成功");
    }
  }
  
  @Override
  public Condition newCondition() {
    throw new UnsupportedOperationException("不支持等待通知操作");
  }
}
